/*
 * Copyright 2024 ywcai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

`include "../core/defines.v"
`include "axi_defines.v"
`timescale 1ns/1ps

module axi_master(
	input	wire						clk,
	input	wire						rst_n,

	input	wire						m_req_i,
	input	wire						m_we_i,
	input	wire[`MemAddrBus]			m_addr_i,
	input	wire[`AxiDataBus]			m_wr_data_i,
	input	wire[11:0]					m_pulse_bits_i,
	input	wire[`AXI_DATA_BITS/8-1:0]	m_strb_i,

	input	wire						m_axi_awReady,
	input	wire						m_axi_wReady,
	input	wire						m_axi_bValid,
	input	wire[1:0]					m_axi_bResp,
	input	wire[7:0]					m_axi_bId,
	input	wire						m_axi_bComp,

	input	wire						m_axi_arReady,
	input	wire						m_axi_rValid,
	input	wire[7:0]					m_axi_rId,
	input	wire						m_axi_rLast,
	input	wire[1:0]					m_axi_rResp,

	input	wire[`RegDataBus]			m_axi_rData,

	output	reg							m_axi_awValid,
	output	reg[`MemAddrBus]			m_axi_awAddr,
	output	reg[7:0]					m_axi_awId,
	output	reg[7:0]					m_axi_awLen,
	output	reg[2:0]					m_axi_awSize,
	output	reg[1:0]					m_axi_awBurst,
	output	reg							m_axi_awLock,
	output	reg[3:0]					m_axi_awCache,
	output	reg[2:0]					m_axi_awProt,
	output	reg[`RegDataBus]			m_axi_wData,
	output	reg[7:0]					m_axi_wStrb,
	output	reg							m_axi_wLast,

	output	reg							m_axi_wValid,
	output	reg							m_axi_bReady,

	output	reg							m_axi_arValid,
	output	reg[`MemAddrBus]			m_axi_arAddr,
	output	reg[7:0]					m_axi_arId,
	output	reg[7:0]					m_axi_arLen,
	output	reg[2:0]					m_axi_arSize,
	output	reg[1:0]					m_axi_arBurst,
	output	reg							m_axi_arLock,
	output	reg[3:0]					m_axi_arCache,
	output	reg[2:0]					m_axi_arProt,

	output	reg							m_axi_rReady,

	output	wire[`AxiDataBus]			m_rd_data_o,
	output	wire[1:0]					errcode,
	output	wire						transact_done_o
	);

	reg[`AxiDataBus]	r_cache;
	reg[15:0]			read_cnt;
	reg[15:0]			write_idx;
	reg					write_cycle_0;
	reg[1:0]			read_delay_cycles;
	reg					wr_done;
	reg					rd_done;
	wire[7:0]			burst_len = m_pulse_bits_i/`XLEN - 1;

	wire wr_request = m_req_i & m_we_i & !wr_done;
	wire rd_request = m_req_i & !m_we_i & !rd_done;

	assign m_rd_data_o = r_cache;
	assign errcode[0] = (m_we_i & m_axi_bResp[1]) | (!m_we_i & m_axi_rResp[1]);
	assign errcode[1] = (m_we_i & m_axi_bResp[1]) | (!m_we_i & m_axi_rResp[1]);
	assign transact_done_o = wr_done | rd_done;

//  write transaction
    always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | m_axi_awValid) begin
			m_axi_awValid <= `DISABLE;
		end else if (wr_request) begin
			m_axi_awValid <= `ENABLE;
		end
	end

	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | wr_done) begin
			m_axi_wValid <= `DISABLE;
			m_axi_awAddr <= `ZERO_ADDR;
			m_axi_awId <= 8'h0;
			m_axi_awLen <= 8'h0;
			m_axi_awSize <= 3'b000;
			m_axi_awBurst <= 2'b00;
			m_axi_awLock <= `DISABLE;
			m_axi_awCache <= 4'b0000;
			m_axi_awProt <= 3'b000;
		end else if (m_axi_awValid) begin
			m_axi_wValid <= `ENABLE;
			m_axi_awAddr <= m_addr_i;
			m_axi_awId <= 8'h1;
			m_axi_awLen <= burst_len;
			m_axi_awSize <= 3'b011;
			m_axi_awBurst <= 2'b01;
			m_axi_awLock <= `ENABLE;
			m_axi_awCache <= 4'b0011;
			m_axi_awProt <= 3'b000;
		end
	end

	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | wr_done) begin
			m_axi_wData <= `XLEN'h0;
			m_axi_wStrb <= 8'h0;
			m_axi_wLast <= `DISABLE;
			write_idx <= 16'h0;
			write_cycle_0 <= `ENABLE;
		end else if(m_axi_wValid) begin
			if (write_cycle_0) begin
				write_cycle_0 <= `DISABLE;
				m_axi_wData <= m_wr_data_i[(write_idx<<6) +: 64];
				m_axi_wStrb <= m_strb_i[(write_idx<<3) +: 8];
			end else begin
				if (write_idx <= m_axi_awLen) begin
					write_idx <= write_idx + 1;
					m_axi_wData <= m_wr_data_i[((write_idx+1)<<6) +: 64];
					m_axi_wStrb <= m_strb_i[((write_idx+1)<<3) +: 8];
					if (write_idx == m_axi_awLen) begin
						m_axi_wLast <= `ENABLE;
					end
				end
			end
		end
	end

	always @(posedge clk) begin
		m_axi_bReady <= `ENABLE;
	end

//  read transaction
	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | m_axi_arValid) begin
			m_axi_arValid <= `DISABLE;
		end else if(rd_request) begin
			m_axi_arValid <= `ENABLE;
		end
	end

	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | rd_done) begin
			m_axi_arAddr <= `ZERO_ADDR;
			m_axi_arId <= 8'b0;
			m_axi_arLen <= 8'h0;
			m_axi_arSize <= 3'b000;
			m_axi_arBurst <= 2'b00;
			m_axi_arLock <= `DISABLE;
			m_axi_arCache <= 4'b0000;
			m_axi_arProt <= 3'b000;
		end else if (m_axi_arValid) begin
			m_axi_arAddr <= m_addr_i;
			m_axi_arId <= 8'h2;
			m_axi_arLen <= burst_len;
			m_axi_arSize <= 3'b011;
			m_axi_arBurst <= 2'b01;
			m_axi_arLock <= `ENABLE;
			m_axi_arCache <= 4'b0011;
			m_axi_arProt <= 3'b000;
		end
	end

	always @(posedge clk) begin
		m_axi_rReady <= `ENABLE;
	end

	//	3 cycles delay needed: 1. transfer addr
	//	2. slave get data 3. transfer data
	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | rd_done) begin
			r_cache <= `AXI_DATA_BITS'h0;
			read_delay_cycles <= 2'b11;
			read_cnt <= 16'h0;
		end else if(m_axi_rValid) begin
			if (read_delay_cycles != 2'b00) begin
				read_delay_cycles <= read_delay_cycles - 1;
			end else begin
                read_cnt <= read_cnt + 1;
				if (read_cnt <= m_axi_arLen) begin
					r_cache[read_cnt<<6 +: 64] <= m_axi_rData;
				end
			end
		end
	end

//------------------------------------------------------------------------------
	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | wr_done) begin
			wr_done <= `DISABLE;
		end else if (m_axi_bComp) begin
			wr_done <= `ENABLE;
		end
	end

	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | rd_done) begin
			rd_done <= `DISABLE;
		end else if (m_axi_rLast) begin
			rd_done <= `ENABLE;
        end
	end

endmodule




module axi_slave(
	input	wire				clk,
	input	wire				rst_n,

	input	wire[`RegDataBus]	s_rd_data_i,
	input	wire[1:0]			s_errcode_i,

	input	wire				s_axi_awValid,
	input	wire[`MemAddrBus]	s_axi_awAddr,
	input	wire[7:0]			s_axi_awId,
	input	wire[7:0]			s_axi_awLen,
	input	wire[2:0]			s_axi_awSize,
	input	wire[1:0]			s_axi_awBurst,
	input	wire				s_axi_awLock,
	input	wire[3:0]			s_axi_awCache,
	input	wire[2:0]			s_axi_awProt,
	input	wire[`RegDataBus]	s_axi_wData,
	input	wire[7:0]			s_axi_wStrb,
	input	wire				s_axi_wLast,

	input	wire				s_axi_wValid,
	input	wire				s_axi_bReady,

	input	wire				s_axi_arValid,
	input	wire[`MemAddrBus]	s_axi_arAddr,
	input	wire[7:0]			s_axi_arId,
	input	wire[7:0]			s_axi_arLen,
	input	wire[2:0]			s_axi_arSize,
	input	wire[1:0]			s_axi_arBurst,
	input	wire				s_axi_arLock,
	input	wire[3:0]			s_axi_arCache,
	input	wire[2:0]			s_axi_arProt,

	input	wire				s_axi_rReady,

	output	reg					s_axi_awReady,
	output	reg					s_axi_wReady,
	output	reg[1:0]			s_axi_bResp,
	output	reg					s_axi_bValid,
	output	reg[7:0]			s_axi_bId,
	output	reg					s_axi_bComp,

	output	reg					s_axi_arReady,
	output	reg[1:0]			s_axi_rResp,
	output	reg					s_axi_rValid,
	output	reg[7:0]			s_axi_rId,
	output	reg					s_axi_rLast,

	output	reg[`RegDataBus]	s_axi_rData,

	output	reg 				s_we_o,
	output	reg[`MemAddrBus]	s_addr_o,
	output	reg[`RegDataBus]	s_wr_data_o,
	output	reg[7:0]			s_strb_o
	);

	reg[7:0]			write_idx;
	reg[7:0]			read_idx;
	reg[`MemAddrBus]	wr_next_addr;
	reg[`MemAddrBus]	rd_next_addr;
	reg					wr_done;
	reg					rd_done;
	reg[1:0]			write_delay_cycles;
	reg					write_cycle_0;
	reg[1:0]			read_delay_cycles;
	reg					read_cycle_0;
	reg[7:0]			read_cnt;
/*
	always @(*) begin
		if (rst_n == `RESET_ENABLE | s_axi_arValid
			| wr_done | rd_done) begin
            s_we_o = `DISABLE;
		end else if (s_axi_awValid) begin
            s_we_o = `ENABLE;
		end else begin
			s_we_o = `DISABLE;
		end
	end
*/
	always @(*) begin
		if (rst_n == `RESET_ENABLE | wr_done | rd_done) begin
			s_addr_o = `ZERO_ADDR;
		end else if (s_axi_awValid) begin
			s_addr_o = s_axi_awAddr;
		end else if (s_axi_wValid) begin
			s_addr_o = wr_next_addr;
		end else if (s_axi_arValid) begin
			s_addr_o = s_axi_arAddr;
		end else if (s_axi_rValid) begin
            s_addr_o = rd_next_addr;
        end else begin
			s_addr_o = `ZERO_ADDR;
		end
	end

//  write transaction
	always @(posedge clk) begin
		s_axi_awReady <= `ENABLE;
	end

	always @(posedge clk) begin
		s_axi_wReady <= `ENABLE;
	end

	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | wr_done) begin
			s_axi_bValid <= `DISABLE;
			wr_next_addr <= `ZERO_ADDR;
			s_wr_data_o <= `XLEN'h0;
            s_strb_o <= 8'h0;
			write_idx <= 8'h0;
			write_delay_cycles <= 2'b01;
			write_cycle_0 <= `ENABLE;
			s_we_o <= `DISABLE;
		end else if (s_axi_wValid) begin
			if (write_delay_cycles != 2'b00) begin
				write_delay_cycles <= write_delay_cycles - 1;
				s_we_o <= `DISABLE;
			end else if (!s_axi_wLast) begin
				if (write_cycle_0) begin
					write_cycle_0 <= `DISABLE;
					wr_next_addr[`ADDR_MSB:`ADDR_LSB] <=
						s_axi_awAddr[`ADDR_MSB:`ADDR_LSB]
							+ {29'h0, write_idx};
					s_wr_data_o <= s_axi_wData;
					s_strb_o <= s_axi_wStrb;
					s_we_o <= `ENABLE;
				end else begin
					write_idx <= write_idx + 1;
					wr_next_addr[`ADDR_MSB:`ADDR_LSB] <=
						s_axi_awAddr[`ADDR_MSB:`ADDR_LSB]
							+ {29'h0, write_idx+8'h1};
					s_wr_data_o <= s_axi_wData;
					s_strb_o <= s_axi_wStrb;
					s_we_o <= `ENABLE;
				end
			end else begin
				s_axi_bValid <= `ENABLE;
				s_we_o <= `DISABLE;
			end
		end
	end

	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | wr_done) begin
			s_axi_bId <= 8'h0;
			s_axi_bComp <= `DISABLE;
			s_axi_bResp <= 2'b00;
		end else if (s_axi_bValid) begin
			s_axi_bId <= s_axi_awId;
			s_axi_bComp <= `ENABLE;
			s_axi_bResp <= {s_errcode_i[1], `DISABLE};
		end
	end

//  read transaction
	always @(posedge clk) begin
		s_axi_arReady <= `ENABLE;
	end

	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | rd_done) begin  
			s_axi_rValid <= `DISABLE;
		end else if (s_axi_arValid) begin
			s_axi_rValid <= `ENABLE;
		end
	end	

	// 2 cycles deay needed for read data: 1. output addr
	// 2. get data 3. write into rData
	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | rd_done) begin
			s_axi_rId <= 8'h0;
            rd_next_addr <= `ZERO_ADDR;
			s_axi_rData <= `XLEN'h0;
			s_axi_rResp <= 2'b00;
			s_axi_rLast <= `DISABLE;
			read_idx <= 8'h0;
			read_delay_cycles <= 2'b10;
			read_cycle_0 <= `ENABLE;
			read_cnt <= 8'h0;
		end else if (s_axi_rValid) begin
			s_axi_rId <= s_axi_arId;
    		if (read_idx < s_axi_arLen) begin
				if (read_cycle_0) begin
					read_cycle_0 <= `DISABLE;
					rd_next_addr[`ADDR_MSB:`ADDR_LSB] <=
						s_axi_arAddr[`ADDR_MSB:`ADDR_LSB]
							+ {29'h0, read_idx};
				end else begin
					read_idx <= read_idx + 1;
					rd_next_addr[`ADDR_MSB:`ADDR_LSB] <=
						s_axi_arAddr[`ADDR_MSB:`ADDR_LSB]
							+ {29'h0, read_idx+8'h1};
				end
            end
			if (read_delay_cycles != 2'b00) begin
				read_delay_cycles <= read_delay_cycles - 1;
			end else begin
				if (read_cnt <= s_axi_arLen) begin
					read_cnt <= read_cnt + 1;
					s_axi_rData <= s_rd_data_i;
					s_axi_rResp <= {s_errcode_i[1], `DISABLE};
					if (read_cnt == s_axi_arLen) begin
						s_axi_rLast <= `ENABLE;
					end
				end
				
			end
		end
	end

//------------------------------------------------------------------------------
	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | wr_done) begin
			wr_done <= `DISABLE;
		end else if (s_axi_bComp) begin
			wr_done <= `ENABLE;
		end
	end

	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE | rd_done) begin
			rd_done <= `DISABLE;
		end else if (s_axi_rLast) begin
			rd_done <= `ENABLE;
		end
	end

endmodule
